#pragma once

#include <utility>

#include <iostream>

#include <string>

#include <memory>

#include <functional>

#include <unordered_map>

#include <cerrno>

#include "log.hpp"

#include "TcpSocket.hpp"

#include "nocopy.hpp"

#include "Epoller.hpp"

#include "Comm.hpp"

const uint32_t EVENT_IN = (EPOLLIN | EPOLLET); // 无论读写事件都设置为ET模式
const uint32_t EVENT_OUT = (EPOLLOUT | EPOLLET);
const uint32_t EVENT_DEL_OP = 0;

const int g_buffer_size = 128;

class Connection;
class TcpServer;

using func_t = std::function<void(std::shared_ptr<Connection>)>;
// 利用 functional 包装器定义一个返回值为 void 参数为Connection智能指针的函数类型 其中func_t为类型名

class Connection
{
public:
    Connection(int sock, std::shared_ptr<TcpServer> tcp_server_ptr) : _sock(sock), _tcp_server_ptr(tcp_server_ptr) {} // connection 的初始化

    void SetHandler(func_t recv_cb, func_t send_cb, func_t except_cb)
    { // 设置回调函数
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _except_cb = except_cb;
    }

    ~Connection() {}

    func_t _recv_cb;   // 读事件函数
    func_t _send_cb;   // 写事件函数
    func_t _except_cb; // 其他事件函数

    void AppendInbuffer(const std::string &info)
    {
        _inbuffer += info;
    }

    // const std::string Cstr()
    // {
    //     return _inbuffer.c_str();
    // }

    void AppendOutBuffer(const std::string &info)
    {
        _outbuffer += info;
    }

    std::string &GetOutBuffer()
    {
        return _outbuffer;
    }

    std::string &GetInBuffer()
    {
        // Caculator 内部会对缓冲区作处理 因此此处直接返回string的引用
        return _inbuffer;
    }

    void PrintInfo()
    {
        std::string info = _inbuffer.c_str() + 0;
        printf("Fd %d Get a message: %s", _sock, info.c_str());
    }

    int GetSockfd()
    {
        return _sock;
    }

    std::string _clientIP;
    uint16_t _clientPort;
    std::shared_ptr<TcpServer> _tcp_server_ptr; // 指向TcpServer的回指指针
    // std::weak_ptr<TcpServer> _tcp_server_ptr; // 指向TcpServer的回指指针 -> 回指指针使用 weak_ptr // bug???????!!!!!!!
private:
    // 每个连接都需要自己的缓冲区
    int _sock;
    std::string _inbuffer; // 此处用string 充当连接的缓冲区,唯一缺陷为无法很好处理二进制流
                           // (可以使用vector 但vector需要进行字符处理 因此此处采用string)
    std::string _outbuffer;
};

class TcpServer : public nocopy /* 防拷贝*/,std::enable_shared_from_this<TcpServer>
{
    static const int _num = 64; // 用户预设空间每次能够容纳就绪事件的大小

public:
    TcpServer(uint16_t port, func_t OnMessage) : _port(port),
                                                 _OnMessage(OnMessage),
                                                 _epoller_ptr(new Epoller),
                                                 _listen_sock_ptr(new NetSocket),
                                                 _quit(true)
    {
    }

    void Init()
    {
        _listen_sock_ptr->Socket();
        SetNonBlockOrDie(_listen_sock_ptr->GetFd());
        _listen_sock_ptr->Bind(_port);
        _listen_sock_ptr->Listen();
        lg(INFO, "listen socket created successfully: %d", _listen_sock_ptr->GetFd());
        AddConnection(_listen_sock_ptr->GetFd(), EVENT_IN,
                      std::bind(&TcpServer::Accepter, this, std::placeholders::_1), /**/
                      nullptr,
                      nullptr,
                      "0.0.0.0", 8050); // 将监听套接字设置进connections中
                                        // 回调方法暂不设置
    }

    void AddConnection(int sock, uint32_t event, func_t recv_cb, func_t send_cb, func_t except_cb, std::string peerip, uint16_t peerport) // 文件描述符 事件 以及回调方法
    {
        /*
            在当前结构中关心套接字文件描述符需要注意两点:
            1. 将事件添加进内核 让Epoll判断哪些文件描述符就绪 以及就绪事件
            2. 需要为套接字建立一个connection对象 将sock添加到
            Connection中,同时将sock和Connection放入哈希表unordered_map的_connections中
        */

        // 将套接字与对应的Connection关联
        // this 传参有问题 报错bug在这!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -> 已解决 强转

        std::shared_ptr<Connection> new_connection = std::make_shared<Connection>(sock, shared_from_this()); // 构建 Connection 对象
        new_connection->SetHandler(recv_cb, send_cb, except_cb);                                                           // 设置回调函数

        // 添加到哈希表中
        _connections.insert(std::make_pair(sock, new_connection));

        // 将套接字加入至关心事件中 关心读事件
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD, sock, event);
        _connections[sock]->_clientIP = peerip;
        _connections[sock]->_clientPort = peerport;

        lg(DEBUG, "Add a new Connection, sockfd: %d", sock);
    }

    void Accepter(std::shared_ptr<Connection> connection) // 连接管理器
    {
        while (1)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = ::accept(connection->GetSockfd(), (sockaddr *)&peer, &len);
            if (sock > 0)
            {
                // 连接获取成功
                uint16_t peerport = ntohs(peer.sin_port);
                char inbuff[64];
                inet_ntop(AF_INET, &peer.sin_addr.s_addr, inbuff, sizeof(inbuff));
                std::string peerip = inbuff;
                lg(DEBUG, "Client IP: %s , Client Port: %d", peerip.c_str(), peerport);

                SetNonBlockOrDie(sock); // 将连接设置为非阻塞
                // AddConnection(sock, EVENT_IN, nullptr, nullptr, nullptr); // 将新连接加入至connections中
                AddConnection(sock, EVENT_IN,
                              std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                              std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                              std::bind(&TcpServer::Excepter, this, std::placeholders::_1),
                              peerip, peerport);
            }
            else
            {
                // 连接获取失败
                if (errno == EWOULDBLOCK) // 连接被设置为非阻塞且当前没有到来(也表示当前连接被获取完毕)
                {
                    break;
                }
                else if (errno == EINTR) // 表示因信号被挂起
                {
                    continue;
                }
                else
                    break;
            }
        }
    }

    // 事件管理器
    void Recver(std::shared_ptr<Connection> connection)
    {
        // 不需要关心数据格式 此程序为服务器 只需关注IO中数据是否被完整读取 至于报文格式细节无需关心
        int fd = connection->GetSockfd();
        std::string clientIP = connection->_clientIP.c_str();
        uint16_t clientPort = connection->_clientPort;
        printf("%s , %d  \n", clientIP.c_str(), clientPort);
        while (true)
        {
            char buff[g_buffer_size];
            memset(buff, 0, sizeof(buff));
            int n = recv(fd, buff, sizeof(buff), 0);
            if (n > 0) // 成功获取
            {
                connection->AppendInbuffer(buff);
            }
            else if (n == 0) // 对端连接关闭
            {
                lg(INFO, "IP: %s, Port: %d, Fd: %d Closed, And I will closed the connection....",
                   clientIP.c_str(),
                   clientPort,
                   connection->GetSockfd());
                connection->_except_cb(connection);
                return;
            }
            else // n<0
            {
                if (errno == EWOULDBLOCK)
                {
                    // 读取完毕
                    break;
                }
                else if (errno == EINTR)
                {
                    // 异常信号中断
                    continue;
                }
                else
                {
                    // 读取出错
                    lg(WARNING, "IP: %s, Port: %d, Fd: %d Recv error",
                       clientIP,
                       clientPort,
                       connection->GetSockfd());
                    connection->_except_cb(connection);
                    return;
                }
            }
        }
        connection->PrintInfo();
        _OnMessage(connection);
    }
    void Sender(std::shared_ptr<Connection> connection)
    {
        auto &outbuffer = connection->GetOutBuffer();

        while (1)
        {
            ssize_t n = send(connection->GetSockfd(), outbuffer.c_str(), outbuffer.size(), 0);
            // 正常对数据进行发送 这里引用是因为方便数据发送后对已发送数据进行处理
            if (n > 0)
            {
                outbuffer.erase(0, n); // 发送成功则清理对应缓冲区大小
                if (outbuffer.empty())
                    break; // 发送完毕
            }
            else if (n == 0)
            {           // 发送完毕
                return; // 表示没发或者发完了
            }
            else
            {
                // n<0 表示发送错误 发送错误可能涉及errno = EWOULDBLOCK(缓冲区不够) 或者 EINTR
                if (errno == EWOULDBLOCK) // 用户层仍有数据未发送完毕 但下层缓冲区可用空间不足
                                          // epoll reactor 将写事件设置为关心
                    break;
                else if (errno == EINTR)
                    continue; // 表示发送时被信号断开
                              // epoll reactor 将写事件设置为关心

                else
                {
                    // 数据发送错误
                    lg(WARNING, "sockfd: %d,server info: %s send error...", connection->GetSockfd(), connection->GetOutBuffer());
                    connection->_except_cb(connection); // 交由异常情况进行处理
                    return;                             // 此处异常
                }
            }
        }
        // 跳出循环有几种可能
        /*
            1. 发送完毕
            2. 读取错误
            3. 被信号断开
            4. 缓冲区不足
            5. 异常情况 (指Epoll模型在等待中所出现的异常情况需要进行处理)

        实际真正的异常情况交由Excepter处理( _except_cb()可调用对象 )
        因此情况只有两种 1. 上层有数据 2. 上层无数据
                上层有数据时需要将写事件设置为关心事件
                上层无数据表示send成功将时间设置为非关心事件(从利用epoll_ctl将事件由红黑树移除)
        */

        if (!outbuffer.empty())
        {
            EnableEvent(connection->GetSockfd(), true, true); // 读  写
        }
        else
        { // 写完则关闭写事件关心
            EnableEvent(connection->GetSockfd(), true, false);
        }
    }

    void EnableEvent(int sock, bool readable, bool writeable)
    {
        uint32_t events = 0;
        events |= ((readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET); // 三木操作符 按位或 来设置一个事件集
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_MOD, sock, events);                    // 调用epoll的更新操作将事件集更新到内核的epoll红黑树中
    }

    void Excepter(std::shared_ptr<Connection> connection)
    {
        // 异常事件处理
        // 1. 移除对异常事件的关心
        // EnableEvent(connection->GetSockfd(),false,false);
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL, connection->GetSockfd(), EVENT_DEL_OP);
        
        // 2. 关闭套接字文件描述符连接
        close(connection->GetSockfd());
        auto it = _connections.find(connection->GetSockfd());
        if (it == _connections.end())
        {
            lg(WARNING, "Erase fd %d error...", connection->GetSockfd());
            exit(5);
        }

        // 3. 移除哈希表中的连接
        _connections[connection->GetSockfd()] = nullptr; // 提前置空避免double free
        // _connections.erase(it); // 
    }

    // -----

    bool IsConnectionSafe(int fd) // 判断连接是否存在
    {
        auto iter = _connections.find(fd);
        if (iter == _connections.end())
        {
            return false; // 连接不存在
        }
        else
            return true;
        // 连接存在则表明连接安全
    }

    void Dispatcher(int timeout) // 事件派发器
    {
        int n = _epoller_ptr->EpollerWait(_revs, _num, timeout);
        for (int i = 0; i < n; ++i)
        {
            uint32_t event = _revs[i].events;
            int sock = _revs[i].data.fd;

            if (event & EPOLLERR)
            {
                event |= (EPOLLIN | EPOLLOUT);
            }
            if (event & EPOLLHUP)
            {
                event |= (EPOLLIN | EPOLLOUT);
            }
            // 当事件为异常事件时将所有的异常事件统一转化为读写问题 进而在后期只需要处理读写即可

            if ((event & EPOLLIN) && IsConnectionSafe(sock)) // 读事件就绪
            {
                if (_connections[sock]->_recv_cb)
                {
                    _connections[sock]->_recv_cb(_connections[sock]);
                }
            }
            if ((event & EPOLLOUT) && IsConnectionSafe(sock)) // 写事件就绪
            {
                if (_connections[sock]->_send_cb)
                {
                    _connections[sock]->_send_cb(_connections[sock]);
                }
            }

            // ..... 其他事件就绪
        }
    }

    void Loop()
    {

        _quit = false;
        while (!_quit)
        {                     // 循环 直至bool类型quit为真
            Dispatcher(3000); // 调用事件派发
            PrintConnection();
        }
    }

    void PrintConnection()
    {
        std::cout << "Connection List: ";
        for (auto &connection : _connections)
        {
            std::cout << connection.second->GetSockfd() << "  ";
        }
        std::cout << std::endl;
    }

    ~TcpServer()
    {
        _listen_sock_ptr->Close();
    }

private:
    uint16_t _port; // 端口
    std::shared_ptr<Epoller> _epoller_ptr;
    std::shared_ptr<NetSocket> _listen_sock_ptr;                       // 服务器监听套接字
    std::unordered_map<int, std::shared_ptr<Connection>> _connections; // 管理当前服务器中的所有连接
    struct epoll_event _revs[_num];                                    // 用户预设空间 用于存放具体事件
    bool _quit;

    // 便于上层处理信息
    func_t _OnMessage; // 要求上层传递一个回调函数来处理数据
                       // 由于Reactor模型是一个IO模型 不具体关注数据是否完整获取或者数据结构细节
                       // 只关注对方所发数据是否全部到达
                       // 故上层在进行数据读取时需要自行判断数据是否为完整数据报文
};
